using System;
using AzureContrib.WindowsAzure.ServiceRuntime;
using Microsoft.ServiceBus;
using Microsoft.ServiceBus.Description;
using Microsoft.ServiceBus.Messaging;
using SDTRace = System.Diagnostics.Trace;

namespace AzureContrib.WindowsAzure.Diagnostics
{
	/// <summary>
	/// A trace listener which writes to <see cref="Microsoft.ServiceBus"/>
	/// </summary>
	public class ServiceBusTraceListener : System.Diagnostics.TraceListener
	{
		/// <summary>
		/// The <see cref="TraceBrokeredMessage"/> used for messages which are not valid <see cref="TraceMessage"/>s.
		/// </summary>
		public const string DefaultSwitch = "Not a message created through ITrace";
		
		/// <summary>
		/// The setting name for the namespace used in the ServiceBus by <see cref="ServiceBusTraceListener"/>.
		/// </summary>
		public const string ServiceBusNameSpaceSettingName = "Namespace for ServiceBusTraceListener";

		/// <summary>
		/// The setting name for the issuer name used in the ServiceBus by <see cref="ServiceBusTraceListener"/>.
		/// </summary>
		public const string ServiceBusIssuerNameSettingName = "IssuerName for ServiceBusTraceListener";

		/// <summary>
		/// The setting name for the issuer key used in the ServiceBus by <see cref="ServiceBusTraceListener"/>.
		/// </summary>
		public const string ServiceBusIssuerKeySettingName = "IssuerKey for ServiceBusTraceListener";

		/// <summary>
		/// The setting name for the topic used in the ServiceBus by <see cref="ServiceBusTraceListener"/>.
		/// </summary>
		public const string ServiceBusTopicSettingName = "Topic for ServiceBusTraceListener";

		/// <summary>
		/// The setting name for the operation timeout used in the ServiceBus by <see cref="ServiceBusTraceListener"/>.
		/// </summary>
		public const string ServiceBusOperationTimeOutSettingSettingName = "Operation Timeout for ServiceBusTraceListener";

		/// <summary>
		/// One minute is the current default.
		/// </summary>
		public readonly TimeSpan DefaultServiceBusOperationTimeOut = TimeSpan.FromMinutes(1);

		private string roleName;
		private string instanceId;
		private string deploymentId;

		private TopicClient topicClient;

		/// <summary>
		/// Fish through the <paramref name="roleEnvironment"/> and make sure we are set to actually attach this listener. If so then attach!
		/// </summary>
		/// <param name="roleEnvironment"><see cref="IRoleEnvironment"/></param>
		public ServiceBusTraceListener(IRoleEnvironment roleEnvironment)
		{
			roleName = roleEnvironment.CurrentRoleInstance.Role.Name;
			instanceId = roleEnvironment.CurrentRoleInstance.Id;
			deploymentId = roleEnvironment.DeploymentId;

			string serviceNamespace;
			if (!roleEnvironment.TryGetConfigurationSettingValue(ServiceBusNameSpaceSettingName, out serviceNamespace))
			{
				TraceHelper.ConfigurationWarning(ServiceBusNameSpaceSettingName, GetType());
				return;
			}

			string issuerName;
			if (!roleEnvironment.TryGetConfigurationSettingValue(ServiceBusIssuerNameSettingName, out issuerName))
			{
				TraceHelper.ConfigurationWarning(ServiceBusTopicSettingName, GetType());
				return;
			}

			string issuerKey;
			if (!roleEnvironment.TryGetConfigurationSettingValue(ServiceBusIssuerKeySettingName, out issuerKey))
			{
				TraceHelper.ConfigurationWarning(ServiceBusIssuerKeySettingName, GetType());
				return;
			}

			string topic;
			if (!roleEnvironment.TryGetConfigurationSettingValue(ServiceBusTopicSettingName, out topic))
			{
				TraceHelper.ConfigurationWarning(ServiceBusTopicSettingName, GetType());
				return;
			}
			
			SharedSecretCredential sharedSecretCredential = TransportClientCredentialBase.CreateSharedSecretCredential(issuerName, issuerKey);

			string operationTimeOutSetting;
			TimeSpan operationTimeout;
			if (!(roleEnvironment.TryGetConfigurationSettingValue(ServiceBusOperationTimeOutSettingSettingName, out operationTimeOutSetting) &&
						TimeSpan.TryParse(operationTimeOutSetting, out operationTimeout)))
			{
				operationTimeout = DefaultServiceBusOperationTimeOut;
			}

			var serviceBusNamespaceClientSettings = new ServiceBusNamespaceClientSettings
									{
										Credential = sharedSecretCredential,
										OperationTimeout = operationTimeout
									};
		
			Uri serviceBusUri = ServiceBusEnvironment.CreateServiceUri("sb", serviceNamespace, string.Empty);

			var messagingFactory = MessagingFactory.Create(serviceBusUri, sharedSecretCredential);
			topicClient = messagingFactory.CreateTopicClient(topic);

			SDTRace.Listeners.Add(this);
		}

		/// <summary>
		/// Remove this trace listener from <see cref="System.Diagnostics.Trace.Listeners"/>
		/// </summary>
		/// <param name="disposing"></param>
		protected override void Dispose(bool disposing)
		{
			SDTRace.Listeners.Remove(this);
			base.Dispose(disposing);
		}

		/// <summary>
		/// Perform a write of a <see cref="TraceBrokeredMessage"/> as a <see cref="TraceType.Write"/> to the <see cref="Microsoft.ServiceBus"/>.
		/// </summary>
		/// <param name="message">The actual trace message to write.</param>
		/// <remarks>Any <see cref="string"/> <paramref name="message"/> works but with full effect only if it is a <see cref="TraceMessage"/>.</remarks>
		public override void Write(string message)
		{
			BrokeredMessage brokeredMessage = GetBrokeredMessage(message, TraceType.Write);
			Send(brokeredMessage);
		}

		/// <summary>
		/// Perform a write of a <see cref="TraceBrokeredMessage"/> as a <see cref="TraceType.WriteLine"/> to the <see cref="Microsoft.ServiceBus"/>.
		/// </summary>
		/// <param name="message">The actual trace message to write.</param>
		/// <remarks>Any <see cref="string"/> <paramref name="message"/> works but with full effect only if it is a <see cref="TraceMessage"/>.</remarks>
		public override void WriteLine(string message)
		{
			BrokeredMessage brokeredMessage = GetBrokeredMessage(message, TraceType.WriteLine);
			Send(brokeredMessage);
		}

		private BrokeredMessage GetBrokeredMessage(string message, TraceType traceType)
		{
			TraceBrokeredMessage traceBrokeredMessage;

			TraceMessage traceMessage;
			if (TraceMessage.TryParse(message, out traceMessage))
			{
				traceBrokeredMessage = new TraceBrokeredMessage(traceType, traceMessage,
																												roleName, instanceId, deploymentId);
			}
			else
			{
				traceMessage = new TraceMessage(null, DefaultSwitch, message);
				traceBrokeredMessage = new TraceBrokeredMessage(traceType, traceMessage, roleName, instanceId, deploymentId);
			}

			return traceBrokeredMessage;
		}

		private void Send(BrokeredMessage brokeredMessage)
		{
			MessageSender messageSender = topicClient.CreateSender();
			messageSender.Send(brokeredMessage);
		}
	}
}